import { ExecutionContext, Realm, Reference } from './runtime';

export class Evaluator {
  private realm = new Realm();

  private globalObject = {};

  private executionContextStack = [
    new ExecutionContext(this.realm, this.globalObject),
  ];

  evaluate(node: Record<string, any>) {
    const type = node.type;
    if ((this as any)[type]) {
      return (this as any)[type](node);
    }
  }

  Program(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  StatementList(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    } else {
      this.evaluate(node.children[0]);
      return this.evaluate(node.children[1]);
    }
  }

  Statement(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  ExpressionStatement(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  Expression(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  AdditiveExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    } else {
    }
  }

  MultiplicativeExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    } else {
    }
  }

  PrimaryExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }
  }

  VariableDeclaration(node: Record<string, any>) {
    console.log("Declare variable", node.children[1].value);
  }

  Literal(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  NumericLiteral(node: Record<string, any>) {
    let str: string = node.value;

    let radix = 10;

    if (str.match(/^0b/)) {
      str = str.substr(2);
      radix = 2;
    } else if (str.match(/^0o/)) {
      str = str.substr(2);
      radix = 8;
    } else if (str.match(/^0x/)) {
      str = str.substr(2);
      radix = 16;
    }

    let value = 0;
    for (let i = 0; i < str.length; i++) {
      let c = str.charCodeAt(i);
      if (c >= "a".charCodeAt(0)) {
        c = c - "a".charCodeAt(0) + 10;
      } else if (c >= "A".charCodeAt(0)) {
        c = c - "A".charCodeAt(0) + 10;
      } else {
        c -= "0".charCodeAt(0);
      }

      value = value * radix + c;
    }

    return value;
  }

  StringLiteral(node: Record<string, any>) {
    let unescapedString: string = node.value;
    let escapedString: string = "";

    const map: Record<string, string> = {
      "\\": "\\",
      "'": "'",
      '"': '"',
      "0": "\0",
      b: "\b",
      f: "\f",
      n: "\n",
      r: "\r",
      t: "\t",
      v: "\v",
    };

    for (let i = 1; i < unescapedString.length - 1; i++) {
      if (unescapedString[i] === "\\") {
        i++;
        if (i < unescapedString.length - 1) {
          escapedString += map[unescapedString[i]] ?? unescapedString[i];
        } else {
          throw new Error("Unexpected end of string");
        }
      } else {
        escapedString += unescapedString[i];
      }
    }

    return escapedString;
  }

  ObjectLiteral(node: Record<string, any>) {
    if (node.children.length === 2) {
      return {};
    }
    if (node.children.length === 3) {
      const object = new Map();

      this.PropertyList(node.children[1], object);

      return object;
    }
  }

  PropertyList(node: Record<string, any>, object: Map<string, any>) {
    if (node.children.length === 1) {
      this.Property(node.children[0], object);
    } else {
      this.PropertyList(node.children[0], object);
      this.Property(node.children[2], object);
    }
  }

  Property(node: Record<string, any>, object: Map<string, any>) {
    let propertyName = "";

    if (node.children[0].type === "IdentifierName") {
      propertyName = node.children[0].value;
    } else if (node.children[0].type === "StringLiteral") {
      propertyName = this.evaluate(node.children[0]);
    }

    object.set(propertyName, {
      value: this.evaluate(node.children[2]),
      writable: true,
      enumerable: true,
      configurable: true,
    });
  }

  AssignmentExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const left = this.evaluate(node.children[0]);
    const right = this.evaluate(node.children[2]);

    left.set(right);
  }

  LogicalORExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const result = this.evaluate(node.children[0]);

    if (result) {
      return result;
    }

    return this.evaluate(node.children[2]);
  }

  LogicalANDExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const result = this.evaluate(node.children[0]);

    if (!result) {
      return result;
    }

    return this.evaluate(node.children[2]);
  }

  LeftHandSideExpression(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  NewExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    if (node.children.length === 2) {
      return this.evaluate(node.children[1]).construct();

      // new 运算符的过程
      // let object = this.realm.Object.construct();

      // const classObject = this.evaluate(node.children[1]);
      // const instance = classObject.call(object);

      // if (typeof instance === 'object') {
      //   return instance;
      // } else {
      //   return object;
      // }
    }
  }

  CallExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    if (node.children.length === 2) {
      const func = this.evaluate(node.children[0]);
      const args = this.evaluate(node.children[1]);
      return func.call(args);
    }
  }

  MemberExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    if (node.children.length === 3) {
      const obj = this.evaluate(node.children[0]).get();
      const prop = obj.get(node.children[2].value);

      if (prop.hasOwnProperty('value')) {
        return prop.value;
      }

      if (prop.hasOwnProperty('get')) {
        return prop.get().call(obj);
      }
    }
  }

  IdentifierName(node: Record<string, any>) {
    const runningExecutionContext = this.executionContextStack[
      this.executionContextStack.length - 1
    ];
    return new Reference(
      runningExecutionContext.lexicalEnvironment,
      node.value,
    );
  }
}
